Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Return local nonce when getTransactionCount request is signed #151

Merged
merged 5 commits into from
Aug 7, 2024

Conversation

ryanschneider
Copy link
Contributor

@ryanschneider ryanschneider commented Aug 5, 2024

📝 Summary

Implements an "MVP" version of #150: When a eth_getTransactionCount RPC request is signed with a X-Flashbots-Signature header matching the target address we return the "next nonce" from Redis instead of proxying the request.

⛱ Motivation and Context

This feature is desirable for Wallets that want to use eth_getTransctionCount to manage user nonces while still preserving users' privacy.

Note that this is the MVP implementation on this functionality, in that it uses the existing RState.GetSenderMaxNonce(addr) check. As noted in #150 this current code does not respect "nonce gaps" so a follow-on PR will be needed later to correctly handle nonce gaps (most likely via adding a per-address Redis Sorted Set ("ZSet") as mentioned in the issue). However, this implementation will work for most scenarios, and will allow us to get feedback from wallets before working on the more-detailed implementation.

📚 References

Tested as follows:

# Check an address nonce
> cast nonce 0x2485aaa7c5453e04658378358f5e028150dc7606 --rpc-url https://rpc-sepolia.flashbots.net --block pending
54

#Send a private tx from this address
cast send .... --rpc-url https://rpc-sepolia.flashbots.net

#Confirm cast nonce still returns the same value
> cast nonce 0x2485aaa7c5453e04658378358f5e028150dc7606 --rpc-url https://rpc-sepolia.flashbots.net --block pending
54

# Call the RPC w/ the X-Flashbots-Signature (code snippet)
responseBytes := sendSignedRPC(t, url, map[string]interface{}{
			"jsonrpc": "2.0",
			"id":      1,
			"method":  "eth_getTransactionCount",
			"params":  []interface{}{"0x2485aaa7c5453e04658378358f5e028150dc7606", "pending"},
		})
{"id":1,"result":"0x37","jsonrpc":"2.0"}

# Once the private tx is mined, confirm both cast nonce and the RPC return 0x37
> cast --to-hex $(cast nonce 0x2485aaa7c5453e04658378358f5e028150dc7606 --rpc-url https://rpc-sepolia.flashbots.net --block pending)
0x37

responseBytes := sendSignedRPC(t, url, map[string]interface{}{
			"jsonrpc": "2.0",
			"id":      1,
			"method":  "eth_getTransactionCount",
			"params":  []interface{}{"0x2485aaa7c5453e04658378358f5e028150dc7606", "pending"},
		})
{"id":1,"result":"0x37","jsonrpc":"2.0"}

✅ I have run these commands

  • make lint
  • make test
  • go mod tidy

@ryanschneider ryanschneider marked this pull request as ready for review August 5, 2024 23:08
@@ -58,7 +60,7 @@ func NewRpcRequest(
rpcCache *application.RpcCache,
) *RpcRequest {
return &RpcRequest{
logger: logger,
logger: logger.With("method", jsonReq.Method),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

return VerifySignature(body, splitSig[0], splitSig[1])
}

func VerifySignature(body []byte, signingAddressStr, signatureStr string) (string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicit names for the return values would increase clarity

Suggested change
func VerifySignature(body []byte, signingAddressStr, signatureStr string) (string, error) {
func VerifySignature(body []byte, signingAddressStr, signatureStr string) (signaturePubkeyHex string, err error) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add in d90fb9b, still need to write unit tests those are coming next.

@@ -134,11 +134,11 @@ func (r *RpcRequestHandler) process() {
r.logger = r.logger.New("rpc_method", jsonReq.Method)

// Process single request
r.processRequest(client, jsonReq, origin, referer, isWhitehatBundleCollection, whitehatBundleId, urlParams, r.req.URL.String())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if a better approach would be not to pass the full body around for possible later signature check, but instead here do a signature check if the header is present, and only pass the result around.

Downside of that approach: doing the signature check also on requests that wouldn't need it. Upside: everything is a bit simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya I like that too, I'll make that change and add tests for the signatures.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to that approach in d90fb9b.

server/request_handler.go Outdated Show resolved Hide resolved
@@ -150,3 +152,48 @@ func (r *RpcRequest) intercept_eth_call_to_FlashRPC_Contract() (requestFinished
r.logger.Info("Intercepted eth_call to FlashRPC contract")
return true
}

func (r *RpcRequest) intercept_signed_eth_getTransactionCount() (requestFinished bool) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love if we could start doing unit / e2e tests for these handlers, but that seems def out of scope for now.

@metachris
Copy link
Collaborator

LGTM! Left a few minor comments.

Most notably, please add tests to the signature methods.

I don't love the name adapters when it's really just utils, but not having a strong opinion on it and it's fine as is.

Comment on lines 158 to 162
if errors.Is(err, flashbots.ErrNoSignature) {
r.logger.Info("[eth_getTransactionCount] No signature found")
return false
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow just realized when refactoring this isn't handling the else case and should be returning an error result. The refactor I'm doing to move the signature check earlier in the flow helps make that more obvious 😊

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d90fb9b.

@ryanschneider ryanschneider merged commit cbffdfd into main Aug 7, 2024
2 checks passed
@ryanschneider ryanschneider deleted the signed-getTransactionCount branch August 7, 2024 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants